package com.lagou.edu.context;

import com.alibaba.druid.util.StringUtils;
import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.annotation.Transactional;
import com.lagou.edu.factory.ProxyFactory;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author ZhangGJ
 * @Date 2021/04/25 07:09
 */
public class ApplicationContext {

    private final String packagePath;

    private final ConcurrentHashMap<String, Object> iocContainer = new ConcurrentHashMap<>();

    public ApplicationContext(String packagePath)
            throws IOException, ClassNotFoundException, InvocationTargetException,
            InstantiationException, IllegalAccessException {
        this.packagePath = packagePath;
        initBeans();
    }

    private void initBeans() throws IOException, ClassNotFoundException, InvocationTargetException,
            InstantiationException, IllegalAccessException {
        instance(classList(packagePath));
    }

    private List<Class<?>> classList(String packagePath)
            throws IOException, ClassNotFoundException {
        List<Class<?>> classList = new ArrayList<>();
        String packageDirName = packagePath.replace(".", "/");
        Enumeration<URL> dirs;
        dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
        while (dirs.hasMoreElements()) {
            URL url = dirs.nextElement();
            String protocol = url.getProtocol();
            if ("file".equals(protocol)) {
                String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                addClass(packagePath, filePath, classList);
            }
        }
        return classList;
    }

    private void instance(List<Class<?>> classList)
            throws ClassNotFoundException, InvocationTargetException, InstantiationException,
            IllegalAccessException {
        for (Class<?> clazz : classList) {
            instanceClassInterface(clazz, classList);
        }
    }

    private void instanceClassInterface(Class<?> clazz, List<Class<?>> classList)
            throws ClassNotFoundException, InvocationTargetException, InstantiationException,
            IllegalAccessException {
        Object obj = null;
        if (clazz.isInterface()) {
            for (Class<?> implClass : classList) {
                if (implClass.isInterface()) {
                    continue;
                }
                Class<?> fieldClassCopy = implClass.getClassLoader().loadClass(clazz.getName());
                if (fieldClassCopy.isAssignableFrom(implClass)) {
                    Constructor<?>[] constructors = implClass.getConstructors();
                    for (Constructor<?> constructor : constructors) {
                        int parameterCount = constructor.getParameterCount();
                        if (parameterCount == 0) {
                            obj = constructor.newInstance();
                        }
                    }
                    break;
                }
            }
        } else {
            Constructor<?>[] constructors = clazz.getConstructors();
            for (Constructor<?> constructor : constructors) {
                int parameterCount = constructor.getParameterCount();
                if (parameterCount == 0) {
                    obj = constructor.newInstance();
                }
            }
        }
        if (obj != null) {
            Service service = Class.forName(clazz.getName()).getAnnotation(Service.class);
            if (service != null && !StringUtils.isEmpty(service.value())) {
                String serviceBeanName = service.value();
                iocContainer.put(serviceBeanName, obj);
            } else {
                iocContainer.put(clazz.getSimpleName(), obj);
            }
        }
    }

    private void addClass(String packagePath, String filePath, List<Class<?>> classList)
            throws ClassNotFoundException {
        File dir = new File(filePath);
        File[] files =
                dir.listFiles(file -> (file.isDirectory() || file.getName().endsWith(".class")));
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    addClass(packagePath + "." + file.getName(), file.getAbsolutePath(), classList);
                } else {
                    String className = file.getName().substring(0, file.getName().length() - 6);
                    classList.add(Class.forName(packagePath + "." + className));
                }
            }
        }
    }

    public Object getBean(String beanName, ApplicationContext applicationContext)
            throws IllegalAccessException {
        Object beanObject = iocContainer.get(beanName);
        injectContainer(beanObject);
        Class<?> clazz = beanObject.getClass();
        Annotation annotation = clazz.getAnnotation(Transactional.class);
        if (annotation != null) {
            ProxyFactory proxyFactory =
                    (ProxyFactory) applicationContext.getBean("ProxyFactory", applicationContext);
            beanObject = proxyFactory.getJdkProxy(beanObject);
        }
        return beanObject;
    }

    private Object injectContainer(Object beanObject) throws IllegalAccessException {
        Class<?> beanClass = beanObject.getClass();
        Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field field : declaredFields) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            Autowired filedAnnotation = field.getAnnotation(Autowired.class);
            if (filedAnnotation == null) {
                break;
            }
            Class<?> fieldClass = field.getType();
            String classSimpleName = fieldClass.getSimpleName();
            Object fieldObject = iocContainer.get(classSimpleName);
            Object object = injectContainer(fieldObject);
            field.set(beanObject, object);
        }
        return beanObject;
    }

}
